package edu.hubu.service;

import edu.hubu.commons.ObjectAssignment;
import edu.hubu.commons.ResultsJson;
import edu.hubu.commons.StatusCode;
import edu.hubu.exception.InformationNotFoundException;
import edu.hubu.exception.InternalServerException;
import net.moon.jpa.dao.IBaseDao;
import net.moon.jpa.model.BaseEntity;
import org.springframework.data.domain.PageRequest;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author moonlan
 * @date 2020/10/20 下午2:22
 */
@Transactional
public abstract class AbstractService<T extends BaseEntity> {
    protected final IBaseDao<T, String> dao;

    protected AbstractService(IBaseDao<T, String> dao) {
        this.dao = dao;
    }

    public T findById(String id) {
        Optional<T> byId = dao.findById(id);
        if (byId.isPresent()) {
            return byId.get();
        } else {
            throw new InformationNotFoundException("未找到相关信息");
        }
    }

    public ResultsJson findByLikePage(int currentPage, int size, Map<String, Object> map) {
        PageRequest request = PageRequest.of(currentPage, size);
        var all = dao.findAll((root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (entry.getKey().endsWith("Forbidden") || entry.getKey().endsWith("Deleted")) {
                    predicates.add(criteriaBuilder.equal(root.get(entry.getKey()), entry.getValue()));
                    continue;
                }
                if (entry.getValue() instanceof Collection) {
                    continue;
                }

                if (entry.getValue() instanceof BaseEntity) {
                    Join<Object, Object> join = root.join(entry.getKey(), JoinType.INNER);
                    Field[] fields = entry.getValue().getClass().getDeclaredFields();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        try {
                            Object value = field.get(entry.getValue());
                            if (value != null && !(value instanceof Collection) && !(value instanceof Boolean)) {
                                predicates.add(criteriaBuilder.equal(join.get(field.getName()), value));
                            }
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    continue;
                }
                predicates.add(criteriaBuilder.like(root.get(entry.getKey()).as(String.class), "%" + entry.getValue() + "%"));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        }, request);
        return ResultsJson.toJson(StatusCode.SUCCESS, all.getTotalElements(), currentPage, all.getContent());
    }


    public ResultsJson findAllPage(int currentPage, int size) {
        PageRequest request = PageRequest.of(currentPage, size);
        var all = dao.findAll((root, query, criteriaBuilder) -> query.getRestriction(), request);
        return ResultsJson.toJson(StatusCode.SUCCESS, all.getTotalElements(), currentPage, all.getContent());
    }

    public ResultsJson updateById(String id, T t, java.util.function.Predicate<? super T> predicate) {
        Optional<T> optional = dao.findById(id).filter(predicate);
        if (optional.isPresent()) {
            T u = optional.get();
            ObjectAssignment.assign(t, u);
            T save = dao.saveAndFlush(u);
            return ResultsJson.toJson(StatusCode.SUCCESS, 1, 0, save);
        }
        throw new InternalServerException();
    }

    public ResultsJson deleteById(String id) {
        try {
            dao.deleteById(id);
            return ResultsJson.toJson(StatusCode.SUCCESS, 1, 0, true);
        } catch (Exception e) {
            throw new InternalServerException(e);
        }
    }

    public ResultsJson add(T t) {
        try {
            T save = dao.save(t);
            return ResultsJson.toJson(StatusCode.SUCCESS, 1, 0, save);
        } catch (Exception e) {
            throw new InternalServerException(e);
        }
    }

    public ResultsJson count(String className) {
        try {
            long count = dao.count((root, query, criteriaBuilder) -> {
                Predicate isDeleted = criteriaBuilder.equal(root.get(className + "IsDeleted"), false);
                return query.where(isDeleted).getRestriction();
            });
            return ResultsJson.toJson(StatusCode.SUCCESS, 1, 0, count);
        } catch (Exception e) {
            throw new InternalServerException(e);
        }
    }
}
